package com.haiya.common.exception;

import com.haiya.common.response.CommonRes;
import com.haiya.common.response.ResCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.sql.SQLException;

/**
 * Servlet应用的统一异常处理
 * 参考链接:
 * - https://www.cnblogs.com/wudimanong/p/10710923.html
 * - https://www.codeprj.com/blog/a58a6f1.html
 * 放在公共包中，考虑到spring cloud gateway应用使用的是WebFlux， 因此需要加入ConditionalOnWebApplicaiton条件注解。
 * 对于spring cloud gateway项目，此文件不生效。否则会报错:
 * Error creating bean with name 'globalExceptionHandler': Lookup method resolution failed;
 * nested exception is java.lang.IllegalStateException: Failed to introspect Class......
 *
 * 而在网关里边定义了JsonErrorWebExceptionHandler:
 * - 下游服务宕机
 * - 下游服务故障
 * 简单的演示方式就是将下游服务关掉，就可以看到类似响应:
 * {
 *     "msg": "下游节点异常",
 *     "code": 40000,
 *     "data": "503 SERVICE_UNAVAILABLE \"Unable to find instance for uaa-server\""
 * }
 *
 * SpringMVC中，只要是经过DispatcherServlet处理的请求，都可以通过@ControllerAdvice和@ExceptionHandler自定义不同类型的异常处理逻辑。
 * 具体可参考ResponseEntityExceptionHandler和DefaultHandlerExceptionResolver, 底层原理很简单，就是发生异常的时候搜索容器中已经存在的异常处理器
 * 并且匹配对应的异常类型，匹配成功之后使用该指定的异常处理器返回结果进行Response渲染，如果找不到默认的异常处理器则用默认的进行兜底。
 * Spring在很多功能设计的时候都有这种"有则使用自定义，无则使用默认提供"的这种思想十分优雅。
 *
 * SpringMVC中提供的自定义异常体系在Spring-WebFlux中并不适用，其实原因很简单，两者底层运行容器并不相同。
 * WebExceptionHandler是Spring-WebFlux的异常处理器顶层接口，因此追溯到子类可以追踪到:
 * - DefaultErrorWebExceptionHandler是Spring Cloud Gateway的全局处理器，配置类是ErrorWebFluxAutoConfiguration。
 *
 * 对于微服务/分布式架构，可将异常可以划分为两种类型:
 * 1. 内部异常: InternalException(自定义)
 * 2. 外部异常: ApiException(自定义)
 *
 * 目前其他模块有单独实现该基础类，然后加上@ControllerAdvice注解。
 *
 * 我们这里添加了条件引入，对于SERVLET对引入。Spring Cloud Gateway引入报错，因为它使用WebFlux，错误拦截方式完全不同。
 */
@ResponseBody
@Slf4j
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ControllerAdvice
public class DefaultExceptionAdvice {
    /**
     * IllegalArgumentException异常处理返回json
     * 返回状态码:400
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({ArgsException.class})
    public CommonRes argsException(ArgsException e) {
        return defHandler(ResCodeEnum.ARGS_ERROR, e);
    }

    /**
     * IllegalArgumentException异常处理返回json
     * 返回状态码:400
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({IllegalArgumentException.class})
    public CommonRes badRequestException(IllegalArgumentException e) {
        return defHandler(ResCodeEnum.ILLEGAL_PARAM, e);
    }

    /**
     * IllegalArgumentException异常处理返回json
     * 返回状态码:400
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler({EntityNotFoundException.class})
    public CommonRes badRequestException(EntityNotFoundException e) {
        return defHandler(ResCodeEnum.ENTITY_NOT_FOUND, e);
    }

    /**
     * AccessDeniedException异常处理返回json
     * 返回状态码:403
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler({AccessDeniedException.class})
    public CommonRes badMethodExpressException(AccessDeniedException e) {
        return defHandler(ResCodeEnum.UNAUTHORISE, e);
    }

    /**
     * 返回状态码:405
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public CommonRes handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return defHandler(ResCodeEnum.REQUEST_METHOD_ERROR, e);
    }

    /**
     * 返回状态码:415
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    public CommonRes handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        return defHandler(ResCodeEnum.MEDIA_TYPE_ERROR, e);
    }

    /**
     * SQLException sql异常处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler({SQLException.class})
    public CommonRes handleSQLException(SQLException e) {
        return defHandler(ResCodeEnum.SQL_ERROR, e);
    }

    /**
     * BusinessException 业务异常处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(BusinessException.class)
    public CommonRes handleException(BusinessException e) {
        return defHandler(ResCodeEnum.BUSINESS_ERROR, e);
    }

    /**
     * IdempotencyException 幂等性异常
     * 返回状态码:200
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(IdempotencyException.class)
    public CommonRes handleException(IdempotencyException e) {
        return defHandler(ResCodeEnum.IDEMPOTENCY_ERROR, e);
    }

    /**
     * 所有异常统一处理
     * 返回状态码:500
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public CommonRes handleException(Exception e) {
        return defHandler(ResCodeEnum.UNKNOWN_ERROR, e);
    }

    private CommonRes defHandler(ResCodeEnum resCodeEnum, Exception e) {
        log.error(resCodeEnum.msg(), e);
        return new CommonRes(resCodeEnum, e.getMessage());
    }
}
